Passed
Branch wavefile-reader (a5ebcc)
by Rafael S.
02:52
created

WaveFileReader.readJunkChunk_   A

Complexity

Conditions 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 13
rs 9.95
c 0
b 0
f 0
cc 2
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileReader class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import RIFFFile from './riff-file';
33
import {unpackString, unpack} from 'byte-data';
34
35
/**
36
 * A class to read wav files.
37
 */
38
export default class WaveFileReader extends RIFFFile {
39
40
  /**
41
   * @param {?Uint8Array=} wavBuffer A wave file buffer.
42
   * @throws {Error} If container is not RIFF, RIFX or RF64.
43
   * @throws {Error} If format is not WAVE.
44
   * @throws {Error} If no 'fmt ' chunk is found.
45
   * @throws {Error} If no 'data' chunk is found.
46
   */
47
  constructor(wavBuffer=null) {
48
    super();
49
    // Include 'RF64' as a supported container format
50
    this.supported_containers.push('RF64');
51
    /**
52
     * The data of the 'fmt' chunk.
53
     * @type {!Object<string, *>}
54
     */
55
    this.fmt = {
56
      /** @type {string} */
57
      chunkId: '',
58
      /** @type {number} */
59
      chunkSize: 0,
60
      /** @type {number} */
61
      audioFormat: 0,
62
      /** @type {number} */
63
      numChannels: 0,
64
      /** @type {number} */
65
      sampleRate: 0,
66
      /** @type {number} */
67
      byteRate: 0,
68
      /** @type {number} */
69
      blockAlign: 0,
70
      /** @type {number} */
71
      bitsPerSample: 0,
72
      /** @type {number} */
73
      cbSize: 0,
74
      /** @type {number} */
75
      validBitsPerSample: 0,
76
      /** @type {number} */
77
      dwChannelMask: 0,
78
      /**
79
       * 4 32-bit values representing a 128-bit ID
80
       * @type {!Array<number>}
81
       */
82
      subformat: []
83
    };
84
    /**
85
     * The data of the 'fact' chunk.
86
     * @type {!Object<string, *>}
87
     */
88
    this.fact = {
89
      /** @type {string} */
90
      chunkId: '',
91
      /** @type {number} */
92
      chunkSize: 0,
93
      /** @type {number} */
94
      dwSampleLength: 0
95
    };
96
    /**
97
     * The data of the 'cue ' chunk.
98
     * @type {!Object<string, *>}
99
     */
100
    this.cue = {
101
      /** @type {string} */
102
      chunkId: '',
103
      /** @type {number} */
104
      chunkSize: 0,
105
      /** @type {number} */
106
      dwCuePoints: 0,
107
      /** @type {!Array<!Object>} */
108
      points: [],
109
    };
110
    /**
111
     * The data of the 'smpl' chunk.
112
     * @type {!Object<string, *>}
113
     */
114
    this.smpl = {
115
      /** @type {string} */
116
      chunkId: '',
117
      /** @type {number} */
118
      chunkSize: 0,
119
      /** @type {number} */
120
      dwManufacturer: 0,
121
      /** @type {number} */
122
      dwProduct: 0,
123
      /** @type {number} */
124
      dwSamplePeriod: 0,
125
      /** @type {number} */
126
      dwMIDIUnityNote: 0,
127
      /** @type {number} */
128
      dwMIDIPitchFraction: 0,
129
      /** @type {number} */
130
      dwSMPTEFormat: 0,
131
      /** @type {number} */
132
      dwSMPTEOffset: 0,
133
      /** @type {number} */
134
      dwNumSampleLoops: 0,
135
      /** @type {number} */
136
      dwSamplerData: 0,
137
      /** @type {!Array<!Object>} */
138
      loops: []
139
    };
140
    /**
141
     * The data of the 'bext' chunk.
142
     * @type {!Object<string, *>}
143
     */
144
    this.bext = {
145
      /** @type {string} */
146
      chunkId: '',
147
      /** @type {number} */
148
      chunkSize: 0,
149
      /** @type {string} */
150
      description: '', //256
151
      /** @type {string} */
152
      originator: '', //32
153
      /** @type {string} */
154
      originatorReference: '', //32
155
      /** @type {string} */
156
      originationDate: '', //10
157
      /** @type {string} */
158
      originationTime: '', //8
159
      /**
160
       * 2 32-bit values, timeReference high and low
161
       * @type {!Array<number>}
162
       */
163
      timeReference: [0, 0],
164
      /** @type {number} */
165
      version: 0, //WORD
166
      /** @type {string} */
167
      UMID: '', // 64 chars
168
      /** @type {number} */
169
      loudnessValue: 0, //WORD
170
      /** @type {number} */
171
      loudnessRange: 0, //WORD
172
      /** @type {number} */
173
      maxTruePeakLevel: 0, //WORD
174
      /** @type {number} */
175
      maxMomentaryLoudness: 0, //WORD
176
      /** @type {number} */
177
      maxShortTermLoudness: 0, //WORD
178
      /** @type {string} */
179
      reserved: '', //180
180
      /** @type {string} */
181
      codingHistory: '' // string, unlimited
182
    };
183
    /**
184
     * The data of the 'ds64' chunk.
185
     * Used only with RF64 files.
186
     * @type {!Object<string, *>}
187
     */
188
    this.ds64 = {
189
      /** @type {string} */
190
      chunkId: '',
191
      /** @type {number} */
192
      chunkSize: 0,
193
      /** @type {number} */
194
      riffSizeHigh: 0, // DWORD
195
      /** @type {number} */
196
      riffSizeLow: 0, // DWORD
197
      /** @type {number} */
198
      dataSizeHigh: 0, // DWORD
199
      /** @type {number} */
200
      dataSizeLow: 0, // DWORD
201
      /** @type {number} */
202
      originationTime: 0, // DWORD
203
      /** @type {number} */
204
      sampleCountHigh: 0, // DWORD
205
      /** @type {number} */
206
      sampleCountLow: 0 // DWORD
207
      /** @type {number} */
208
      //'tableLength': 0, // DWORD
209
      /** @type {!Array<number>} */
210
      //'table': []
211
    };
212
    /**
213
     * The data of the 'data' chunk.
214
     * @type {!Object<string, *>}
215
     */
216
    this.data = {
217
      /** @type {string} */
218
      chunkId: '',
219
      /** @type {number} */
220
      chunkSize: 0,
221
      /** @type {!Uint8Array} */
222
      samples: new Uint8Array(0)
223
    };
224
    /**
225
     * The data of the 'LIST' chunks.
226
     * Each item in this list look like this:
227
     *  {
228
     *      chunkId: '',
229
     *      chunkSize: 0,
230
     *      format: '',
231
     *      subChunks: []
232
     *   }
233
     * @type {!Array<!Object>}
234
     */
235
    this.LIST = [];
236
    /**
237
     * The data of the 'junk' chunk.
238
     * @type {!Object<string, *>}
239
     */
240
    this.junk = {
241
      /** @type {string} */
242
      chunkId: '',
243
      /** @type {number} */
244
      chunkSize: 0,
245
      /** @type {!Array<number>} */
246
      chunkData: []
247
    };
248
    /**
249
     * @type {!Object}
250
     * @protected
251
     */
252
    this.uInt16 = {bits: 16, be: false};
253
    // Load a file from the buffer if one was passed
254
    // when creating the object
255
    if (wavBuffer) {
256
      this.fromBuffer(wavBuffer);
257
    }
258
  }
259
260
  /**
261
   * Set up the WaveFileReader object from a byte buffer.
262
   * @param {!Uint8Array} wavBuffer The buffer.
263
   * @param {boolean=} samples True if the samples should be loaded.
264
   * @throws {Error} If container is not RIFF, RIFX or RF64.
265
   * @throws {Error} If format is not WAVE.
266
   * @throws {Error} If no 'fmt ' chunk is found.
267
   * @throws {Error} If no 'data' chunk is found.
268
   */
269
  fromBuffer(wavBuffer, samples=true) {
270
    this.clearHeader();
271
    this.setSignature(wavBuffer);
272
    this.uInt16.be = this.uInt32.be;
273
    if (this.format != 'WAVE') {
274
      throw Error('Could not find the "WAVE" format identifier');
275
    }
276
    this.readDs64Chunk_(wavBuffer);
277
    this.readFmtChunk_(wavBuffer);
278
    this.readFactChunk_(wavBuffer);
279
    this.readBextChunk_(wavBuffer);
280
    this.readCueChunk_(wavBuffer);
281
    this.readSmplChunk_(wavBuffer);
282
    this.readDataChunk_(wavBuffer, samples);
283
    this.readJunkChunk_(wavBuffer);
284
    this.readLISTChunk_(wavBuffer);
285
  }
286
287
  /**
288
   * Return the value of a RIFF tag in the INFO chunk.
289
   * @param {string} tag The tag name.
290
   * @return {?string} The value if the tag is found, null otherwise.
291
   */
292
  getTag(tag) {
293
    /** @type {!Object} */
294
    let index = this.getTagIndex_(tag);
295
    if (index.TAG !== null) {
296
      return this.LIST[index.LIST].subChunks[index.TAG].value;
297
    }
298
    return null;
299
  }
300
301
  /**
302
   * Return a Object<tag, value> with the RIFF tags in the file.
303
   * @return {!Object<string, string>} The file tags.
304
   */
305
  listTags() {
306
    /** @type {?number} */
307
    let index = this.getLISTINFOIndex_();
308
    /** @type {!Object} */
309
    let tags = {};
310
    if (index !== null) {
311
      for (let i = 0, len = this.LIST[index].subChunks.length; i < len; i++) {
312
        tags[this.LIST[index].subChunks[i].chunkId] =
313
          this.LIST[index].subChunks[i].value;
314
      }
315
    }
316
    return tags;
317
  }
318
319
  /**
320
   * Return an array with all cue points in the file, in the order they appear
321
   * in the file.
322
   * The difference between this method and using the list in WaveFile.cue
323
   * is that the return value of this method includes the position in
324
   * milliseconds of each cue point (WaveFile.cue only have the sample offset)
325
   * @return {!Array<!Object>}
326
   */
327
  listCuePoints() {
328
    /** @type {!Array<!Object>} */
329
    let points = this.getCuePoints_();
330
    for (let i = 0, len = points.length; i < len; i++) {
331
      points[i].milliseconds =
332
        (points[i].dwPosition / this.fmt.sampleRate) * 1000;
333
    }
334
    return points;
335
  }
336
337
  /**
338
   * Reset some attributes of the object.
339
   * @protected
340
   * @ignore
341
   */
342
  clearHeader() {
343
    this.fmt.cbSize = 0;
344
    this.fmt.validBitsPerSample = 0;
345
    this.fact.chunkId = '';
346
    this.ds64.chunkId = '';
347
  }
348
349
  /**
350
   * Return an array with all cue points in the file, in the order they appear
351
   * in the file.
352
   * @return {!Array<!Object>}
353
   * @private
354
   */
355
  getCuePoints_() {
356
    /** @type {!Array<!Object>} */
357
    let points = [];
358
    for (let i = 0, len = this.cue.points.length; i < len; i++) {
359
      points.push({
360
        dwPosition: this.cue.points[i].dwPosition,
361
        label: this.getLabelForCuePoint_(
362
          this.cue.points[i].dwName)});
363
    }
364
    return points;
365
  }
366
367
  /**
368
   * Return the label of a cue point.
369
   * @param {number} pointDwName The ID of the cue point.
370
   * @return {string}
371
   * @private
372
   */
373
  getLabelForCuePoint_(pointDwName) {
374
    /** @type {?number} */
375
    let cIndex = this.getAdtlChunk_();
376
    if (cIndex !== null) {
377
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
378
        if (this.LIST[cIndex].subChunks[i].dwName ==
379
            pointDwName) {
380
          return this.LIST[cIndex].subChunks[i].value;
381
        }
382
      }
383
    }
384
    return '';
385
  }
386
387
  /**
388
   * Return the index of the INFO chunk in the LIST chunk.
389
   * @return {?number} the index of the INFO chunk.
390
   * @private
391
   */
392
  getLISTINFOIndex_() {
393
    /** @type {?number} */
394
    let index = null;
395
    for (let i = 0, len = this.LIST.length; i < len; i++) {
396
      if (this.LIST[i].format === 'INFO') {
397
        index = i;
398
        break;
399
      }
400
    }
401
    return index;
402
  }
403
404
  /**
405
   * Return the index of the 'adtl' LIST in this.LIST.
406
   * @return {?number}
407
   * @private
408
   */
409
  getAdtlChunk_() {
410
    for (let i = 0, len = this.LIST.length; i < len; i++) {
411
      if (this.LIST[i].format == 'adtl') {
412
        return i;
413
      }
414
    }
415
    return null;
416
  }
417
418
  /**
419
   * Return the index of a tag in a FILE chunk.
420
   * @param {string} tag The tag name.
421
   * @return {!Object<string, ?number>}
422
   *    Object.LIST is the INFO index in LIST
423
   *    Object.TAG is the tag index in the INFO
424
   * @private
425
   */
426
  getTagIndex_(tag) {
427
    /** @type {!Object<string, ?number>} */
428
    let index = {LIST: null, TAG: null};
429
    for (let i = 0, len = this.LIST.length; i < len; i++) {
430
      if (this.LIST[i].format == 'INFO') {
431
        index.LIST = i;
432
        for (let j=0, subLen = this.LIST[i].subChunks.length; j < subLen; j++) {
433
          if (this.LIST[i].subChunks[j].chunkId == tag) {
434
            index.TAG = j;
435
            break;
436
          }
437
        }
438
        break;
439
      }
440
    }
441
    return index;
442
  }
443
444
  /**
445
   * Read the 'fmt ' chunk of a wave file.
446
   * @param {!Uint8Array} buffer The wav file buffer.
447
   * @throws {Error} If no 'fmt ' chunk is found.
448
   * @private
449
   */
450
  readFmtChunk_(buffer) {
451
    /** @type {?Object} */
452
    let chunk = this.findChunk('fmt ');
453
    if (chunk) {
454
      this.head = chunk.chunkData.start;
455
      this.fmt.chunkId = chunk.chunkId;
456
      this.fmt.chunkSize = chunk.chunkSize;
457
      this.fmt.audioFormat = this.readUInt16(buffer);
458
      this.fmt.numChannels = this.readUInt16(buffer);
459
      this.fmt.sampleRate = this.readUInt32(buffer);
460
      this.fmt.byteRate = this.readUInt32(buffer);
461
      this.fmt.blockAlign = this.readUInt16(buffer);
462
      this.fmt.bitsPerSample = this.readUInt16(buffer);
463
      this.readFmtExtension_(buffer);
464
    } else {
465
      throw Error('Could not find the "fmt " chunk');
466
    }
467
  }
468
469
  /**
470
   * Read the 'fmt ' chunk extension.
471
   * @param {!Uint8Array} buffer The wav file buffer.
472
   * @private
473
   */
474
  readFmtExtension_(buffer) {
475
    if (this.fmt.chunkSize > 16) {
476
      this.fmt.cbSize = this.readUInt16(buffer);
477
      if (this.fmt.chunkSize > 18) {
478
        this.fmt.validBitsPerSample = this.readUInt16(buffer);
479
        if (this.fmt.chunkSize > 20) {
480
          this.fmt.dwChannelMask = this.readUInt32(buffer);
481
          this.fmt.subformat = [
482
            this.readUInt32(buffer),
483
            this.readUInt32(buffer),
484
            this.readUInt32(buffer),
485
            this.readUInt32(buffer)];
486
        }
487
      }
488
    }
489
  }
490
491
  /**
492
   * Read the 'fact' chunk of a wav file.
493
   * @param {!Uint8Array} buffer The wav file buffer.
494
   * @private
495
   */
496
  readFactChunk_(buffer) {
497
    /** @type {?Object} */
498
    let chunk = this.findChunk('fact');
499
    if (chunk) {
500
      this.head = chunk.chunkData.start;
501
      this.fact.chunkId = chunk.chunkId;
502
      this.fact.chunkSize = chunk.chunkSize;
503
      this.fact.dwSampleLength = this.readUInt32(buffer);
504
    }
505
  }
506
507
  /**
508
   * Read the 'cue ' chunk of a wave file.
509
   * @param {!Uint8Array} buffer The wav file buffer.
510
   * @private
511
   */
512
  readCueChunk_(buffer) {
513
    /** @type {?Object} */
514
    let chunk = this.findChunk('cue ');
515
    if (chunk) {
516
      this.head = chunk.chunkData.start;
517
      this.cue.chunkId = chunk.chunkId;
518
      this.cue.chunkSize = chunk.chunkSize;
519
      this.cue.dwCuePoints = this.readUInt32(buffer);
520
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
521
        this.cue.points.push({
522
          dwName: this.readUInt32(buffer),
523
          dwPosition: this.readUInt32(buffer),
524
          fccChunk: this.readString(buffer, 4),
525
          dwChunkStart: this.readUInt32(buffer),
526
          dwBlockStart: this.readUInt32(buffer),
527
          dwSampleOffset: this.readUInt32(buffer),
528
        });
529
      }
530
    }
531
  }
532
533
  /**
534
   * Read the 'smpl' chunk of a wave file.
535
   * @param {!Uint8Array} buffer The wav file buffer.
536
   * @private
537
   */
538
  readSmplChunk_(buffer) {
539
    /** @type {?Object} */
540
    let chunk = this.findChunk('smpl');
541
    if (chunk) {
542
      this.head = chunk.chunkData.start;
543
      this.smpl.chunkId = chunk.chunkId;
544
      this.smpl.chunkSize = chunk.chunkSize;
545
      this.smpl.dwManufacturer = this.readUInt32(buffer);
546
      this.smpl.dwProduct = this.readUInt32(buffer);
547
      this.smpl.dwSamplePeriod = this.readUInt32(buffer);
548
      this.smpl.dwMIDIUnityNote = this.readUInt32(buffer);
549
      this.smpl.dwMIDIPitchFraction = this.readUInt32(buffer);
550
      this.smpl.dwSMPTEFormat = this.readUInt32(buffer);
551
      this.smpl.dwSMPTEOffset = this.readUInt32(buffer);
552
      this.smpl.dwNumSampleLoops = this.readUInt32(buffer);
553
      this.smpl.dwSamplerData = this.readUInt32(buffer);
554
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
555
        this.smpl.loops.push({
556
          dwName: this.readUInt32(buffer),
557
          dwType: this.readUInt32(buffer),
558
          dwStart: this.readUInt32(buffer),
559
          dwEnd: this.readUInt32(buffer),
560
          dwFraction: this.readUInt32(buffer),
561
          dwPlayCount: this.readUInt32(buffer),
562
        });
563
      }
564
    }
565
  }
566
567
  /**
568
   * Read the 'data' chunk of a wave file.
569
   * @param {!Uint8Array} buffer The wav file buffer.
570
   * @param {boolean} samples True if the samples should be loaded.
571
   * @throws {Error} If no 'data' chunk is found.
572
   * @private
573
   */
574
  readDataChunk_(buffer, samples) {
575
    /** @type {?Object} */
576
    let chunk = this.findChunk('data');
577
    if (chunk) {
578
      this.data.chunkId = 'data';
579
      this.data.chunkSize = chunk.chunkSize;
580
      if (samples) {
581
        this.data.samples = buffer.slice(
582
          chunk.chunkData.start,
583
          chunk.chunkData.end);
584
      }
585
    } else {
586
      throw Error('Could not find the "data" chunk');
587
    }
588
  }
589
590
  /**
591
   * Read the 'bext' chunk of a wav file.
592
   * @param {!Uint8Array} buffer The wav file buffer.
593
   * @private
594
   */
595
  readBextChunk_(buffer) {
596
    /** @type {?Object} */
597
    let chunk = this.findChunk('bext');
598
    if (chunk) {
599
      this.head = chunk.chunkData.start;
600
      this.bext.chunkId = chunk.chunkId;
601
      this.bext.chunkSize = chunk.chunkSize;
602
      this.bext.description = this.readString(buffer, 256);
603
      this.bext.originator = this.readString(buffer, 32);
604
      this.bext.originatorReference = this.readString(buffer, 32);
605
      this.bext.originationDate = this.readString(buffer, 10);
606
      this.bext.originationTime = this.readString(buffer, 8);
607
      this.bext.timeReference = [
608
        this.readUInt32(buffer),
609
        this.readUInt32(buffer)];
610
      this.bext.version = this.readUInt16(buffer);
611
      this.bext.UMID = this.readString(buffer, 64);
612
      this.bext.loudnessValue = this.readUInt16(buffer);
613
      this.bext.loudnessRange = this.readUInt16(buffer);
614
      this.bext.maxTruePeakLevel = this.readUInt16(buffer);
615
      this.bext.maxMomentaryLoudness = this.readUInt16(buffer);
616
      this.bext.maxShortTermLoudness = this.readUInt16(buffer);
617
      this.bext.reserved = this.readString(buffer, 180);
618
      this.bext.codingHistory = this.readString(
619
        buffer, this.bext.chunkSize - 602);
620
    }
621
  }
622
623
  /**
624
   * Read the 'ds64' chunk of a wave file.
625
   * @param {!Uint8Array} buffer The wav file buffer.
626
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
627
   * @private
628
   */
629
  readDs64Chunk_(buffer) {
630
    /** @type {?Object} */
631
    let chunk = this.findChunk('ds64');
632
    if (chunk) {
633
      this.head = chunk.chunkData.start;
634
      this.ds64.chunkId = chunk.chunkId;
635
      this.ds64.chunkSize = chunk.chunkSize;
636
      this.ds64.riffSizeHigh = this.readUInt32(buffer);
637
      this.ds64.riffSizeLow = this.readUInt32(buffer);
638
      this.ds64.dataSizeHigh = this.readUInt32(buffer);
639
      this.ds64.dataSizeLow = this.readUInt32(buffer);
640
      this.ds64.originationTime = this.readUInt32(buffer);
641
      this.ds64.sampleCountHigh = this.readUInt32(buffer);
642
      this.ds64.sampleCountLow = this.readUInt32(buffer);
643
      //if (wav.ds64.chunkSize > 28) {
644
      //  wav.ds64.tableLength = unpack(
645
      //    chunkData.slice(28, 32), uInt32_);
646
      //  wav.ds64.table = chunkData.slice(
647
      //     32, 32 + wav.ds64.tableLength);
648
      //}
649
    } else {
650
      if (this.container == 'RF64') {
651
        throw Error('Could not find the "ds64" chunk');
652
      }
653
    }
654
  }
655
656
  /**
657
   * Read the 'LIST' chunks of a wave file.
658
   * @param {!Uint8Array} buffer The wav file buffer.
659
   * @private
660
   */
661
  readLISTChunk_(buffer) {
662
    /** @type {?Object} */
663
    let listChunks = this.findChunk('LIST', true);
664
    if (listChunks !== null) {
665
      for (let j=0; j < listChunks.length; j++) {
666
        /** @type {!Object} */
667
        let subChunk = listChunks[j];
668
        this.LIST.push({
669
          chunkId: subChunk.chunkId,
670
          chunkSize: subChunk.chunkSize,
671
          format: subChunk.format,
672
          subChunks: []});
673
        for (let x=0; x<subChunk.subChunks.length; x++) {
674
          this.readLISTSubChunks_(subChunk.subChunks[x],
675
            subChunk.format, buffer);
676
        }
677
      }
678
    }
679
  }
680
681
  /**
682
   * Read the sub chunks of a 'LIST' chunk.
683
   * @param {!Object} subChunk The 'LIST' subchunks.
684
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
685
   * @param {!Uint8Array} buffer The wav file buffer.
686
   * @private
687
   */
688
  readLISTSubChunks_(subChunk, format, buffer) {
689
    if (format == 'adtl') {
690
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
691
        this.head = subChunk.chunkData.start;
692
        /** @type {!Object<string, string|number>} */
693
        let item = {
694
          chunkId: subChunk.chunkId,
695
          chunkSize: subChunk.chunkSize,
696
          dwName: this.readUInt32(buffer)
697
        };
698
        if (subChunk.chunkId == 'ltxt') {
699
          item.dwSampleLength = this.readUInt32(buffer);
700
          item.dwPurposeID = this.readUInt32(buffer);
701
          item.dwCountry = this.readUInt16(buffer);
702
          item.dwLanguage = this.readUInt16(buffer);
703
          item.dwDialect = this.readUInt16(buffer);
704
          item.dwCodePage = this.readUInt16(buffer);
705
        }
706
        item.value = this.readZSTR(buffer, this.head);
707
        this.LIST[this.LIST.length - 1].subChunks.push(item);
708
      }
709
    // RIFF INFO tags like ICRD, ISFT, ICMT
710
    } else if(format == 'INFO') {
711
      this.head = subChunk.chunkData.start;
712
      this.LIST[this.LIST.length - 1].subChunks.push({
713
        chunkId: subChunk.chunkId,
714
        chunkSize: subChunk.chunkSize,
715
        value: this.readZSTR(buffer, this.head)
716
      });
717
    }
718
  }
719
720
  /**
721
   * Read the 'junk' chunk of a wave file.
722
   * @param {!Uint8Array} buffer The wav file buffer.
723
   * @private
724
   */
725
  readJunkChunk_(buffer) {
726
    /** @type {?Object} */
727
    let chunk = this.findChunk('junk');
728
    if (chunk) {
729
      this.junk = {
730
        chunkId: chunk.chunkId,
731
        chunkSize: chunk.chunkSize,
732
        chunkData: [].slice.call(buffer.slice(
733
          chunk.chunkData.start,
734
          chunk.chunkData.end))
735
      };
736
    }
737
  }
738
739
  /**
740
   * Read bytes as a ZSTR string.
741
   * @param {!Uint8Array} bytes The bytes.
742
   * @param {number} index the index to start reading.
743
   * @return {string} The string.
744
   * @protected
745
   */
746
  readZSTR(bytes, index=0) {
747
    for (let i = index; i < bytes.length; i++) {
748
      this.head++;
749
      if (bytes[i] === 0) {
750
        break;
751
      }
752
    }
753
    return unpackString(bytes, index, this.head - 1);
754
  }
755
756
  /**
757
   * Read a number from a chunk.
758
   * @param {!Uint8Array} bytes The chunk bytes.
759
   * @return {number} The number.
760
   * @protected
761
   */
762
  readUInt16(bytes) {
763
    /** @type {number} */
764
    let value = unpack(bytes, this.uInt16, this.head);
765
    this.head += 2;
766
    return value;
767
  }
768
}
769